﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace NEMproject
{

    public enum TARIFFS { Tar05, Tar06, Tar07, Tar08 };     
    public struct Output
    {
        public int yearHorizon;
        public double[] totalSavings;
        public double netPresentValue;
        public double initialCost;
        public double maintenanceCost;

    }

    class NEMmain
    {
       


        /*<<<constants */
       
        private const int _BIMONTHS = 6; //costs in AHK are every two months        
        private const double coef_fuel = 0.00119;
        private const double VAT_rate = 0.18;
        static int[] daysInBiMonth = { 59, 61, 61, 62, 61, 61 };
        /*>>>constants*/

        private double[] CFP = { 610.75, 642.05, 678.91, 701.95, 725.37, 747.16, 
                               784.13, 819.75, 854.40, 880.35, 897.84, 923.78, 
                               938.10, 968.29, 1011.46, 1036.67, 1071.70, 1151.40, 
                               1182.56, 1196.37 }; //CFP is given for the next 20 years 

        private TARIFFS tariff;
        private int yearHorizon;
        private int baseFuelPrice;     
        private bool enerCredTransferable = false;
        private double discountRate;       
        private int[] cumHE = new int[6]; //{ 700, 550, 700, 800, 650, 700 };       
        private int[] solarIrr = new int[6]; 

        public PVSystem pvSystem = new PVSystem();
        public NoPvSystem noPvSystem = new NoPvSystem();

        #region setters_getters

        public int BIMONTHS
        {
            get { return _BIMONTHS; }
        }
        public TARIFFS Tariff
        {
            set { this.tariff = value; }
            get { return this.tariff; }
        }
        public int YearHorizon
        {
            set { this.yearHorizon = value; }
            get { return this.yearHorizon; }
        }
        public int BaseFuelPrice
        {
            set { this.baseFuelPrice = value; }
            get { return this.baseFuelPrice; }
        }
        public bool EnerCredTransferable
        {
            set { this.enerCredTransferable = value; }
            get { return this.enerCredTransferable; }
        }
        public int[] CumHE
        {
            set { this.cumHE = value; }
            get { return this.cumHE; }
        }
        public int[] SolarIrr
        {
            set { this.solarIrr = value; }
            get { return this.solarIrr; }
        }
        public double DiscountRate
        {
            set { this.discountRate = value; }
            get { return this.discountRate; }
        }

        #endregion


        //<<<constructor
        public NEMmain(){
            //give default values to objects
            tariff = TARIFFS.Tar05;
            yearHorizon = 20;
            baseFuelPrice = 300;
            enerCredTransferable = false;
            discountRate = 0.1;
       
        }
        //>>>end constructor



        public Output runMain()
        {
            
            double noPvSystemCharge = 0;
            double pvSystemCharge = 0;
            double energyCredit = 0;     //the amount of energy that is transfered to the next bimonth when generation in kW is greater than consumption
            double PVgen = 0;  //
            double[,] Savings = new double[BIMONTHS, yearHorizon];
            double[] totalSaves;
            double NetPresentValue = 0; // NPV,IRR


            for (int year = 0; year < yearHorizon; year++)
            {
                if (!enerCredTransferable){
                    energyCredit = 0; //energy credit is not being transfered to the next year
                }

                for (int twomonth = 0; twomonth < BIMONTHS; twomonth++)
                {

                    PVgen = pvSystem.computePVgen(solarIrr[twomonth], year, daysInBiMonth[twomonth] );

                    noPvSystemCharge = noPvSystem.computeCharge(cumHE[twomonth] /* * (1 + normrnd(0, 0.1))*/, CFP[year], baseFuelPrice);
                    pvSystemCharge = pvSystem.computeCharge( cumHE[twomonth], PVgen, CFP[year], baseFuelPrice, ref energyCredit);

                    Savings[twomonth, year] = noPvSystemCharge - pvSystemCharge;

                    //Console.WriteLine("Savings are: "+Savings[twomonth, year].ToString()+ " for year: " + year.ToString() + " and twomonth: "+ (twomonth+1).ToString());         
                }            
            }

            NetPresentValue = FinancialAnalysis(ref Savings, out totalSaves, discountRate, pvSystem.PvInitialCost, pvSystem.PvAnnualMaintCost);

            //prepare output
            Output output = new Output();
            output.yearHorizon = yearHorizon;
            output.totalSavings = totalSaves;
            output.netPresentValue = NetPresentValue;

            output.initialCost = pvSystem.PvInitialCost;
            output.initialCost = pvSystem.PvAnnualMaintCost;

            return output;
        }


        //FinancialAnalysis
        double FinancialAnalysis( ref double [,] savings, out double [] totalSaves, double discountRate, double initialCost, double maintenanceCost ){

            totalSaves = new double[yearHorizon];
            double totalSavings=0;         
            for (int year = 0; year < yearHorizon; year++)
            {
                
                double yearSavings=0;                
                for (int twomonth = 0; twomonth < BIMONTHS; twomonth++)
                {
                    yearSavings += savings[twomonth, year];                
                }

                totalSavings += (yearSavings - maintenanceCost) / Math.Pow(1 + discountRate, year - 1);
                totalSaves[year] = totalSavings;     
            }

            return totalSavings - initialCost;
        }

      
        public static double normrnd(double mu, double sigma)
        {
            return RandomGen.GetNormal(mu, sigma);
        }

       
        //<<<Classes
        public class SystemParentClass
        {
            private int[] priceThr = new int[4];  //Prices thresholds for different cumulative energy consumption           
            private double[] varPr = new double[5];    //tariffa            
            private double[] fixedPr = new double[5];  //pagio

            #region setters-getters
            public int[] PriceThr
            {
                set { this.priceThr = value; }
                get { return this.priceThr; }
            }
            public double[] VarPr
            {
                set { this.varPr = value; }
                get { return this.varPr; }
            }
            public double[] FixedPr
            {
                set { this.fixedPr = value; }
                get { return this.fixedPr; }
            }
            #endregion

            public SystemParentClass()
            {
                priceThr[0] = 120; priceThr[1] = 320; priceThr[2] = 500; priceThr[3] = 1000;
                varPr[0] = 0.1371; varPr[1] = 0.1453; varPr[2] = 0.1498; varPr[3] = 0.1541; varPr[4] = 0.1558;
                fixedPr[0] = 2.28; fixedPr[1] = 2.35; fixedPr[2] = 3.86; fixedPr[3] = 5.87; fixedPr[4] = 7.39;  
            }


            /// <summary>
            /// Computes the power station charge for a two month period 
            /// </summary>
            /// <param name="houseConsumption"> The energy consumption in kW for a two month period</param>
            /// <returns></returns>
            public double powerStationChargeFormula(double houseConsumption) {
                
                double chargeHouse = 0;

                int thresholdIndex = 0;
                while (thresholdIndex < priceThr.Length && houseConsumption > priceThr[thresholdIndex]) //find the price threshold for the current consumption
                { 
                    thresholdIndex++;
                }

                
                for (int i = 0; i <= thresholdIndex; i++) { 
                    
                    if ( i >= thresholdIndex ){
                        chargeHouse += (houseConsumption - (i < 1 ? 0 : priceThr[i - 1]) ) * varPr[i] + fixedPr[i];               
                    }
                    else{
                        chargeHouse += (priceThr[i] - ( i<1? 0:priceThr[i-1] )) * varPr[i];
                    }
                }
                return chargeHouse;
                
            }

            //abstract method to be inherited
            public double computeCharge() {

                return -1;
            }
        
        }
       
        public class NoPvSystem : SystemParentClass
        {
            const double ch_RES = 0.005;
            const double coef_YDO = 0.00134;


            //<<<constructor
            public NoPvSystem()
            {            
            }
            //>>>constructor


            /// <summary>
            /// Given a bimonth consumption and current fuel price, this function computes the bimonth charge assuming NO PV is installed 
            /// </summary>
            /// <param name="local_CumHE"> The bimonthly consumption in 'kW' for two months</param>
            /// <param name="CFP"> Current fuel price for this year</param>
            /// <returns></returns>
            public double computeCharge(double cumHE, double currentFuelPrice, double baseFuelPrice)
            {

                double FuelAdj = ((currentFuelPrice - baseFuelPrice) * coef_fuel) / 5;
                
                double ChargeHouse = powerStationChargeFormula( cumHE );
                
                double RES_HOUSE = cumHE * ch_RES;
                double YDO_HOUSE = cumHE * coef_YDO;
                double FuelCharge_HOUSE = cumHE * FuelAdj;

                double totalCharge = (ChargeHouse + FuelCharge_HOUSE + YDO_HOUSE) * (1 + VAT_rate) + RES_HOUSE;

                return totalCharge;

            }


        }

        public class PVSystem : SystemParentClass
        {
            const double DSMK = 1.48;             // Euros/kW
            const double ANC_SER = 3.50;          // Euros/kW
            const double LT_RESERVE = 1.53;       // Euros/kW
            const double TRANSMISSION = 3.98;     // Euros/kW
            const double DISTRIBUTION = 12.31;    // Euros/kW
            const double LV = 20.41;              // Euros/kW
            const double LOSSES = -20;            // Euros/kW
            const double DIVERSIFICATION = 13.81; // Euros/kW
            const double RES = 8.05;              // Euros/kW
            const double YDO = 2.16;              // Euros/kW



            private double pvInstCap;     //installed capacity of the pv system            
            private double pvArea;        // PV module area in m2/kW
            private double pvEfficiancy;
            private double pvLosses;
            private double pvDegradation;
            private double pvInitialCost;     //initial cost of the system
            private double pvAnnualMaintCost;     //initial cost of the system
            private int[] pVperkW = new int[6]; //{ 220, 310, 360, 400, 320, 250 };   //this has to be computed           

            #region setters_getters
            
            public double PvInstCap
            {
                set { pvInstCap = value; }
                get { return pvInstCap; }
            }
            public double PvArea
            {
                set { this.pvArea = value; }
                get { return this.pvArea; }
            }
            public double PvEfficiancy
            {
                set { this.pvEfficiancy = value; }
                get { return this.pvEfficiancy; }
            }
            public double PvLosses
            {
                set { this.pvLosses = value; }
                get { return this.pvLosses; }
            }
            public double PvDegradation
            {
                set { this.pvDegradation = value; }
                get { return this.pvDegradation; }
            }
            public double PvInitialCost
            {
                set { this.pvInitialCost = value; }
                get { return this.pvInitialCost; }
            }
            public double PvAnnualMaintCost
            {
                set { this.pvAnnualMaintCost = value; }
                get { return this.pvAnnualMaintCost; }
            }            
            
            #endregion


            
            
            //<<<constructor
            public PVSystem()
            {                
            }
            //>>>constructor


            /// <summary>
            /// Given a bimonth consumption and current fuel price, this function computes the bimonth charge assuming a PV is installed 
            /// </summary>
            /// <param name="cumHE">The bimonthly consumption in kw of the house</param>
            /// <param name="PVgen">The bimonthly generation in kw for two months of the PV system</param>
            /// <param name="CurrentFuelPrice"></param>
            /// <param name="energyCredit"></param>
            /// <returns> The total charge for the house taking into account the PV system, for the </returns>
            public double computeCharge(int cumHE, double PVgen, double currentFuelPrice, double baseFuelPrice, ref double energyCredit)
            {
                double totalCharge = 0;
                double ANNUAL_PV_CHARGE = (DSMK + ANC_SER + LT_RESERVE + TRANSMISSION + DISTRIBUTION + LV + LOSSES + DIVERSIFICATION + RES + YDO) * pvInstCap * (1 + VAT_rate);
                double BIMONTHLY_PV_CHARGE = ANNUAL_PV_CHARGE / 6; //how much does PV cost for maintenance
                double FuelAdj = ((currentFuelPrice - baseFuelPrice) * coef_fuel) / 5;
               
                double AggHE = cumHE - PVgen - energyCredit; //aggregate house energy
              
                if (AggHE >= 0) //does the house consumes more energy than what the PV generates (+energy Credit from last month)
                {                  
                    double ChargeHouse = powerStationChargeFormula( cumHE );
                    double FuelCharge_HOUSE = AggHE * FuelAdj;

                    totalCharge = (ChargeHouse + FuelCharge_HOUSE) * (1 + VAT_rate) + BIMONTHLY_PV_CHARGE;
                    energyCredit = 0;
                }
                else
                {
                    totalCharge = BIMONTHLY_PV_CHARGE;
                    energyCredit = Math.Abs(AggHE);
                }

                return totalCharge;
            }

            
            /// <summary>
            /// Because pv systems degrate over time this function estimates the energy generation of the pv system for the requested year, based on 
            /// its initial generation values (when degradation was zero -- first year). 
            /// </summary>
            /// <param name="PVperkW"> The bimonthly energy generation in kW of the PV system</param>
            /// <param name="year"> The year for which we will estimate the </param>
            /// <returns> The energy generation of the PV system on a specific month after n years </returns>
            public double computePVgen(int bimonthSolarIrrData, int year, int daysInBimonth) {

                double PVperkW = bimonthSolarIrrData /* / 2 */ * daysInBimonth * pvEfficiancy * pvArea * (1 - pvLosses) / 1000;

                if (year > 0)
                {
                    //the new year is the first year * degradation * some randomness
                    return PVperkW * pvInstCap * Math.Pow(1 - pvDegradation, year - 1) /** (1 + normrnd(0, 0.075))*/;
                }
                else {
                    return PVperkW * pvInstCap;
                }
            }


        }//class PVSystem

        //>>>Classes

    }
}
